package dao.core;

import dao.core.*;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.PersistenceContextType;

import org.hibernate.Criteria;
import org.hibernate.LockOptions;
import org.hibernate.Session;
import org.hibernate.criterion.CriteriaSpecification;
import org.hibernate.criterion.Criterion;
import org.hibernate.criterion.Example;
import org.hibernate.criterion.Order;
import org.hibernate.criterion.Projections;
import org.hibernate.criterion.Restrictions;

public abstract class GenericHibernateDAO<T, ID extends Serializable>
implements GenericDAO<T, ID> {

@PersistenceContext(type = PersistenceContextType.EXTENDED)
private EntityManager entityManager;

/**
* the persistent class.
*/
private final Class<T> persistentClass;

/**
* session.
*/
private Session session;

/**
* @param persistentClass
*            set the persistentClass
*/
public GenericHibernateDAO(final Class<T> persistentClass) {
this.persistentClass = persistentClass;
}

/**
* @param s
*            set the session
*/
public void setSession(final Session s) {
this.session = s;
}

/**
* @return the session
*/
protected Session getSession() {

if ((session == null || !session.isOpen()) && entityManager != null) {
	session = (Session) entityManager.getDelegate();
}

if (session == null) {
	throw new IllegalStateException(
			"Session has not been set on DAO before usage");
}

return session;
}

/**
* @return the persistent class
*/
public Class<T> getPersistentClass() {
return persistentClass;
}

/**
* @see hu.uszeged.inf.stud.prf.lesson6.dao.GenericDao#findById(java.io.Serializable,
*      boolean)
*/
@Override
@SuppressWarnings("unchecked")
public T findById(final ID id, final boolean lock) {

T entity;
if (lock) {
	entity = (T) getSession().load(getPersistentClass(), id,
			LockOptions.UPGRADE);
} else {
	entity = (T) getSession().load(getPersistentClass(), id);
}

return entity;
}
 
@Override
@SuppressWarnings("unchecked")
public List<T> findByExample(final T exampleInstance,
	final String[] excludeProperty) {
final Criteria crit = getSession().createCriteria(getPersistentClass());
final Example example = Example.create(exampleInstance);
if (null != excludeProperty) {
	for (final String exclude : excludeProperty) {
		example.excludeProperty(exclude);
	}
}
crit.add(example);
return crit.list();
}

@Override
public T makePersistent(final T entity) {
getSession().saveOrUpdate(entity);
return entity;
}

@Override
@SuppressWarnings("unchecked")
public ID save(final T entity) {
return (ID) this.getSession().save(entity);
}

@Override
public void update(final T entity) {
this.getSession().update(entity);
}

@Override
public void persist(final T entity) {
this.getSession().persist(entity);
}

@Override
@SuppressWarnings("unchecked")
public T merge(final T entity) {
return (T) this.getSession().merge(entity);
}

@Override
public void makeTransient(final T entity) {
getSession().delete(entity);
}

@Override
public void flush() {
getSession().flush();
}

@Override
public void clear() {
getSession().clear();
}

/**
* @param entity
*            a persistent instance
* @see hu.scm.gremon.dao.GenericDAO#evict(Object)
*/
@Override
public void evict(final T entity) {
getSession().evict(entity);
}

/**
* @param entity
* @return
*/
@Override
public boolean isTransient(final T entity) {
return !this.getSession().contains(entity);
}

/**
* @param entity
* @return
*/
@Override
public boolean isPersistent(final T entity) {
return this.getSession().contains(entity);
}

@Override
public List<T> findByCriteria(final Criterion... criterion) {
return findByCriteria(criterion, new Order[] {});
}

@Override
@SuppressWarnings("unchecked")
public List<T> findByCriteria(final Criterion[] criterion,
	final Order[] order) {
final Criteria crit = getSession().createCriteria(getPersistentClass());
if (null != criterion) {
	for (final Criterion c : criterion) {
		crit.add(c);
	}
}
if (null != order) {
	for (final Order o : order) {
		crit.addOrder(o);
	}
}
return crit.list();
}

@SuppressWarnings("unchecked")
public List<T> findByCriteria(final Criterion[] criterion,
	final Order[] order, int firstResult, int resultCount) {
final Criteria crit = getSession().createCriteria(getPersistentClass());
if (null != criterion) {
	for (final Criterion c : criterion) {
		crit.add(c);
	}
}
if (null != order) {
	for (final Order o : order) {
		crit.addOrder(o);
	}
}
crit.setFirstResult(firstResult);
crit.setMaxResults(resultCount);
return crit.list();
}

@Override
public List<T> findByCriteriaAfterClear(final Criterion... criterion) {
getSession().flush();
getSession().clear();
return findByCriteria(criterion);
}

@Override
@SuppressWarnings("unchecked")
public List<T> findByDistinctCriteria(final Criterion... criterion) {
final Criteria crit = getSession().createCriteria(getPersistentClass());
crit.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
for (final Criterion c : criterion) {
	crit.add(c);
}
return crit.list();
}

/**
* TODO: Test it! Use this query method if you want to get a pageable query!
* 
*/
@Override
public List<T> findByDistinctPageableCriteria(final int page,
	final int itemsPerPage, final Criterion... criterion) {
return findByDistinctPageableCriteria(page, itemsPerPage, criterion,
		null);
}

@SuppressWarnings("unchecked")
public List<T> findByDistinctPageableCriteria(final int page,
	final int itemsPerPage, final Criterion[] criterion, Order[] order) {
/*
 * Get id
 */
final Criteria idsCriteria = getSession().createCriteria(
		getPersistentClass());
idsCriteria.setFirstResult((page - 1) * itemsPerPage);
idsCriteria.setMaxResults(itemsPerPage);
idsCriteria.setProjection(Projections.distinct(Projections
		.property("id")));
for (final Criterion c : criterion) {
	idsCriteria.add(c);
}

final List<?> ids = idsCriteria.list();

if (0 == ids.size()) {
	return new ArrayList<T>();
}

/*
 * Get objects
 */
final Criteria criteria = getSession().createCriteria(
		getPersistentClass());
criteria.setFirstResult(0);
criteria.setMaxResults(Integer.MAX_VALUE);
for (final Criterion c : criterion) {
	criteria.add(c);
}
criteria.add(Restrictions.in("id", ids));

if (null != order) {
	for (final Order o : order) {
		criteria.addOrder(o);
	}
}

return criteria.list();
}

@Override
public long countByDistinctCriteria(final Criterion... criterion) {

final Criteria criteria = getSession().createCriteria(
		getPersistentClass());
criteria.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
criteria.setProjection(Projections.rowCount());
for (final Criterion c : criterion) {
	criteria.add(c);
}
final Long count = (Long) criteria.uniqueResult();

return count.longValue();
}

@Override
public void refresh(final Object object) {
getSession().refresh(object);
}

public T getReference(ID identifier) {
return entityManager.getReference(persistentClass, identifier);
}

}
